package org.example.core.proxy;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.extra.spring.SpringUtil;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.ReflectorFactory;
import org.apache.ibatis.reflection.factory.DefaultObjectFactory;
import org.apache.ibatis.reflection.factory.ObjectFactory;
import org.apache.ibatis.reflection.wrapper.DefaultObjectWrapperFactory;
import org.apache.ibatis.reflection.wrapper.ObjectWrapperFactory;
import org.apache.ibatis.session.SqlSession;
import org.example.annotation.Many;
import org.example.annotation.One;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

public class QueryResultProxy implements MethodInterceptor {

    private ObjectFactory objectFactory =  new DefaultObjectFactory();

    private ObjectWrapperFactory objectWrapperFactory =  new DefaultObjectWrapperFactory();

    private ReflectorFactory reflectorFactory = new DefaultReflectorFactory();

    private ConcurrentMap<String,String> concurrentMap = new ConcurrentHashMap<>();

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        String filedName = getFiledName(method);

        Field field = ReflectionUtils.findField(o.getClass(), filedName);
        if(Objects.nonNull(field) && (field.isAnnotationPresent(Many.class) || field.isAnnotationPresent(One.class))){
            doQuery(o,field,filedName);
        }

        return methodProxy.invokeSuper(o,objects);
    }


    public void doQuery(Object obj,Field field,String filedName){
        if(concurrentMap.containsKey(filedName)){return;}
        SqlSession sqlSession = SpringUtil.getBean(SqlSession.class);
        MetaObject metaObject = MetaObject.forObject(obj, objectFactory, objectWrapperFactory, reflectorFactory);
        if(field.isAnnotationPresent(Many.class)){
            Many many = field.getAnnotation(Many.class);
            String property = many.property();
            String method = many.method();
            Object param = metaObject.getValue(property);
            List<Object> objects = sqlSession.selectList(method, param);
            metaObject.setValue(filedName,objects);
            concurrentMap.put(filedName,filedName);
        }else {
            One one = field.getAnnotation(One.class);
            String method = one.method();
            String property = one.property();
            Object value = metaObject.getValue(property);
            Object object = sqlSession.selectOne(method, value);
            metaObject.setValue(filedName,object);
            concurrentMap.put(filedName,filedName);
        }
    }


    private String getFiledName(Method method){
        String methodName = method.getName();
        if(methodName.startsWith("get")){
            String me = methodName.replace("get", "");
            return getFirstWordLower(me);
        }
        return methodName;
    }

    private String getFirstWordLower(String word){
        char[] chars = word.toCharArray();
        chars[0] = (char)(chars[0] + 32);
        return String.valueOf(chars);
    }

    public static Object create(Object source){
        Enhancer enhancer = new Enhancer();

        enhancer.setSuperclass(source.getClass());

        enhancer.setCallback(new QueryResultProxy());

        Object o = enhancer.create();

        BeanUtil.copyProperties(source,o);
        return o;
    }
}
